using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Linq;

public class MonsterBehaviour : MonoBehaviour {

	public float speed = 5f;
	float chargeSpeedModifier = 1.5f;

	public int inSlowZone=0;
	public float chillTime = 0;

	static List<MonsterBehaviour> allMonsters;

	Waypoint target;
	Waypoint currentWP;
	List <Waypoint> path;

	ShootsBullets shootsBullets;

	public enum AI { GoesToBase, Static };
	public AI ai = AI.GoesToBase;

	float shootingRadius = 10f;
	float chargingRadius = 5f;

	public float dealsChargeDamage=1;

	void Start() {
		if(allMonsters == null) {
			allMonsters = new List<MonsterBehaviour>();
		}

		allMonsters.Add(this);

		GameObject pbGO = GameObject.Find ("playerBase");
		if(pbGO != null)
			target = pbGO.GetComponent<Waypoint>();

		shootsBullets = GetComponent<ShootsBullets>();

		// Increase hitpoints based on wave #
		if(gameObject.layer == 8) {
		}
		else {
			Health h = GetComponent<Health>();
			h.initHPs *= GameManager.Instance.DifficultyExponential();
			h.hps = h.initHPs;

			speed *= Mathf.Min (3f, GameManager.Instance.DifficultyMultiplier());
		}
	}

	void ResetPathfinding() {
		currentWP = GetNearestWP();
		path = null;
		if(target != null)
			path = GetPathFromTo(currentWP, target);
	}

	List <Waypoint> GetPathFromTo(Waypoint from, Waypoint to) {
		Waypoint[] wps = GameObject.FindObjectsOfType<Waypoint>();
		Dictionary<Waypoint, float> dist = new Dictionary<Waypoint, float>();
		Dictionary<Waypoint, Waypoint> previous = new Dictionary<Waypoint, Waypoint>();
		dist[from] = 0;
		previous[from] = null;

		List<Waypoint> unvisitedNodes = new List<Waypoint>(); // Unvisited nodes

		foreach(Waypoint v in wps) {
			if(v != from) {
				dist[v] = Mathf.Infinity;
				previous[v] = null;
			}

			unvisitedNodes.Add(v);
		}

		while(unvisitedNodes.Count > 0) {
			Waypoint u = null;

			foreach(Waypoint possU in unvisitedNodes) {
				if(u==null || dist[possU] < dist[u])
					u = possU;
			}

			if(u==to) {
				break;
			}

			unvisitedNodes.Remove(u);

			foreach(Waypoint v2 in u.GetNeighbours()) {
				float alt = dist[u] + Vector2.Distance(v2.transform.position, u.transform.position);
				if(alt < dist[v2]) {
					dist[v2] = alt;
					previous[v2] = u;
				}
			}
		}

		List<Waypoint> S = new List<Waypoint>();
		Waypoint u2 = to;
		while(previous[u2] != null) {
			S.Add (u2);
			u2 = previous[u2];
		}
		S.Reverse();
		return S;
	}

	Waypoint GetNearestWP() {
		Waypoint[] wps = GameObject.FindObjectsOfType<Waypoint>();

		Waypoint nearest = null;
		float dist = 0;

		foreach(Waypoint w in wps) {
			if(nearest==null || Vector2.Distance(w.transform.position, transform.position) < dist) {
				nearest = w;
				dist = Vector2.Distance(w.transform.position, transform.position);
			}
		}

		return nearest;
	}

	void OnDisable() {
		allMonsters.Remove(this);
	}
	void OnDestroy() {
		allMonsters.Remove(this);
	}

	void Update() {

		if(DoChargingAI() == false) {
			Vector2 avoidanceDir = GetAvoidanceDir();

			rigidbody2D.velocity = GetPathingDir();

			if(rigidbody2D.velocity.magnitude > 0.1f) {
				transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(rigidbody2D.velocity.y, rigidbody2D.velocity.x) * Mathf.Rad2Deg - 90f);
			}

			if(avoidanceDir.magnitude != 0) {
				rigidbody2D.velocity += avoidanceDir.normalized * speed;
				rigidbody2D.velocity /= 2f;
			}
		}

		SpriteRenderer sr = GetComponent<SpriteRenderer>();
		chillTime -= Time.deltaTime;
		if(inSlowZone > 0 || chillTime > 0) {
			sr.color = Color.blue;
			rigidbody2D.velocity /= 2f * GameManager.Instance.slowZoneEffectiveness;
		}
		else if( GetComponent<Health>().IsBurning() ) {
			sr.color = Color.red;
		}
		else {
			sr.color=Color.white;
		}

		/*if(rigidbody2D.velocity.x < 0)
			transform.localScale = new Vector2( -Mathf.Abs(transform.localScale.x), transform.localScale.y );
		else
			transform.localScale = new Vector2(  Mathf.Abs(transform.localScale.x), transform.localScale.y );*/

		DoShootingAI();
	}

	int EnemyLayerMask() {
		return gameObject.layer==8 ? 1 << 10 : 1 << 8;
	}

	void OnCollisionEnter2D( Collision2D colision ) {
		OnTriggerEnter2D(colision.collider);
	}

	void OnTriggerEnter2D( Collider2D col ) {
		if(dealsChargeDamage==0)
			return;

		Health h = col.GetComponent<Health>();
		if(h != null) {
			h.TakeDamage(dealsChargeDamage);
			Destroy (gameObject);
		}
	}


	bool DoChargingAI() {
		if(dealsChargeDamage==0)
			return false;

		Collider2D[] cols = Physics2D.OverlapCircleAll(transform.position, chargingRadius, EnemyLayerMask());
		
		Vector2 dir;
		Transform t=null;
		float dist = Mathf.Infinity;
		
		foreach(Collider2D col in cols) {

			Health h = col.GetComponent<Health>();
			if(h==null)
				continue;

			float d = Vector2.Distance(col.transform.position, transform.position);
			if(t==null || d < dist) {
				dir = col.transform.position - transform.position;
				RaycastHit2D hitTest = Physics2D.Raycast(transform.position, dir, chargingRadius, EnemyLayerMask() | 1 << 0);
				if(hitTest.collider == col.transform.collider2D) {
					t = col.transform;
					dist = d;
				}
			}
		}
		
		if(t==null)
			return false;
		
		dir = t.position - transform.position;
		transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f);

		rigidbody2D.velocity = dir.normalized * speed*chargeSpeedModifier;

		return true;
	}

	void DoShootingAI() {
		if(shootsBullets != null) {
			Collider2D[] cols = Physics2D.OverlapCircleAll(transform.position, shootingRadius, EnemyLayerMask());

			Vector2 dir;
			Transform t=null;
			float dist = Mathf.Infinity;

			foreach(Collider2D col in cols) {
				float d = Vector2.Distance(col.transform.position, transform.position);
				if(t==null || d < dist) {
					dir = col.transform.position - transform.position;
					RaycastHit2D hitTest = Physics2D.Raycast(transform.position, dir, shootingRadius, EnemyLayerMask() | 1 << 0);
					if(hitTest.collider == col.transform.collider2D) {
						t = col.transform;
						dist = d;
					}
				}
			}

			if(t==null)
				return;

			dir = t.position - transform.position;
			transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f);
			shootsBullets.Fire();
		}
	}

	Vector2 GetPathingDir() {
		if(ai == AI.Static)
			return Vector2.zero;

		if(currentWP == null) {
			ResetPathfinding();
			return Vector2.zero;
		}

		if(Vector2.Distance(transform.position, currentWP.transform.position) < 1.0f) {
			currentWP = GetNextWP();
			if(currentWP == null) {
				ResetPathfinding();
				return Vector2.zero;
			}
		}

		if(Vector2.Distance(currentWP.transform.position, transform.position) < 0.5f) {
			return Vector2.zero;
		}

		return (currentWP.transform.position - transform.position).normalized * speed;
	}

	Waypoint GetNextWP() {
		if(currentWP == null) {
			path = null;
			return null;
		}

		if(path==null || path.Count==0) {
//			Debug.LogError("Empty path!");
			return currentWP;
		}

		Waypoint w = path[0];
		path.RemoveAt(0);
		return w;
	}
	
	Waypoint GetRandomWP() {
		if(currentWP == null) {
			return null;
		}

		if(currentWP.edges.Count==0) {
			Debug.LogError("Waypoint '"+currentWP.gameObject.name+"' has no edges.");
			return currentWP;
		}
		
		return currentWP.edges[ Random.Range(0, currentWP.edges.Count) ].wp;
	}

	Collider2D[] nearbyColliders = null;

	void UpdateNearby(float maxAvoidanceRange) {
		nearbyColliders = Physics2D.OverlapCircleAll(transform.position, maxAvoidanceRange, 1 << gameObject.layer);
	}

	float nearbyUpdateDelay = 0;

	Vector2 GetAvoidanceDir() {
		Vector2 avoidanceDir = Vector2.zero;

		float maxAvoidanceRange = GetComponent<Collider2D>().bounds.size.sqrMagnitude * 0.80f;

		nearbyUpdateDelay -= Time.deltaTime;
		if(nearbyColliders==null || nearbyUpdateDelay <= 0) {
			UpdateNearby(maxAvoidanceRange);
			nearbyUpdateDelay = Random.Range(0.05f, 0.15f);
		}
		
		foreach(Collider2D mb in nearbyColliders) {
			if(mb == null || mb == this.collider2D || mb.gameObject.name=="Player")
				continue;
			
			Vector2 diff = this.transform.position - mb.transform.position;
			
			if(diff.sqrMagnitude >= maxAvoidanceRange)
				continue;
			
			if(diff.magnitude == 0) {
				diff = Quaternion.Euler(0, 0, Random.Range(0, 360)) * new Vector2( 0.0001f, 0);
			}
			
			avoidanceDir += diff / ( diff.sqrMagnitude );
			
		}

		return avoidanceDir;
	}

}
